﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Linq;
using System.Runtime.InteropServices;
using System.Windows.Forms;
using System.Runtime.InteropServices.ComTypes;

namespace GDFlacTool.BurnAudio
{
    public partial class FormBurnAudio : Form
    {
        #region Fields.

        //private Int64 _totalDiscSize;
        private const string ClientName = "AudioCD";
        private bool _isBurning;
        private bool _isFormatting;
        private readonly BurnData _burnData = new BurnData();
        private readonly List<string> _files; 

        #endregion

        #region Constructors.

        public FormBurnAudio(List<string> files)
        {
            InitializeComponent();

            _files = files;
        }

        #endregion

        #region Form Events.

        /// <summary>
        /// Initialize the form
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void MainForm_Load(object sender, EventArgs e)
        {
            //
            // Determine the current recording devices
            //
            var discMaster = new MsftDiscMaster2();

            if (!discMaster.IsSupportedEnvironment)
            {
                return;
            }
                
            foreach (string uniqueRecorderId in discMaster)
            {
                var discRecorder2 = new MsftDiscRecorder2();

                discRecorder2.InitializeDiscRecorder(uniqueRecorderId);

                cboDevices.Items.Add(discRecorder2);
            }

            if (cboDevices.Items.Count > 0)
            {
                cboDevices.SelectedIndex = 0;
            }

            chkEjectFormat.Checked = Properties.BurnAudioSettings.Default.chkEjectFormat_Checked;
            chkQuickFormat.Checked = Properties.BurnAudioSettings.Default.chkQuickFormat_Checked;

            DetectMedia();

            AddFiles();

            lblCDProgress.Text = string.Empty;
            lblStatusText.Text = string.Empty;
            lblBufferStatus.Text = string.Empty;
            lblCdBurnElapsedTime.Text = string.Empty;
            lblCdBurnEstimatedTime.Text = string.Empty;

            cdNotify1.Start();
        }
        
        private void FormBurnAudio_FormClosing(object sender, FormClosingEventArgs e)
        {
            cdNotify1.Stop();

            Properties.BurnAudioSettings.Default.chkEjectFormat_Checked = chkEjectFormat.Checked;
            Properties.BurnAudioSettings.Default.chkQuickFormat_Checked = chkQuickFormat.Checked;

            Properties.BurnAudioSettings.Default.Save();

            foreach (MsftDiscRecorder2 discRecorder2 in cboDevices.Items)
            {
                if (discRecorder2 != null)
                {
                    Marshal.ReleaseComObject(discRecorder2);
                }
            }
        }

        #endregion

        #region Format Disc.

        private void btnFormat_Click(object sender, EventArgs e)
        {
            if (cboDevices.SelectedIndex == -1)
            {
                return;
            }

            _isFormatting = true;
            EnableFormatUi(false);

            var discRecorder = (IDiscRecorder2)cboDevices.Items[cboDevices.SelectedIndex];
            backgroundFormatWorker.RunWorkerAsync(discRecorder.ActiveDiscRecorder);
        }

        /// <summary>
        /// Enables/Disables the "Burn" User Interface
        /// </summary>
        /// <param name="enable"></param>
        private void EnableFormatUi(bool enable)
        {
            btnFormat.Enabled = enable;
            chkEjectFormat.Enabled = enable;
            chkQuickFormat.Enabled = enable;
            cboDevices.Enabled = enable;
            lblDevices.Enabled = enable;
            txtMediaType.Enabled = enable;
            ControlBox = enable;

            if (enable)
            {
                lblElapsedTime.Text = @"Elapsed time:";
                lblEstimatedTime.Text = @"Estimated time:";
                lblFormatStatusText.Text = string.Empty;
            }
        }

        /// <summary>
        /// Worker thread that Formats the Disc
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void backgroundFormatWorker_DoWork(object sender, DoWorkEventArgs e)
        {
            MsftDiscRecorder2 discRecorder = null;
            MsftDiscFormat2Erase discFormatErase = null;

            try
            {
                //
                // Create and initialize the IDiscRecorder2
                //
                discRecorder = new MsftDiscRecorder2();
                var activeDiscRecorder = (string)e.Argument;
                discRecorder.InitializeDiscRecorder(activeDiscRecorder);

                //
                // Create the IDiscFormat2Erase and set properties
                //
                discFormatErase = new MsftDiscFormat2Erase
                {
                    Recorder = discRecorder,
                    ClientName = ClientName,
                    FullErase = !chkQuickFormat.Checked
                };

                //
                // Setup the Update progress event handler
                //
                discFormatErase.Update += discFormatErase_Update;

                //
                // Erase the media here
                //
                try
                {
                    discFormatErase.EraseMedia();
                    e.Result = 0;
                }
                catch (COMException ex)
                {
                    e.Result = ex.ErrorCode;
                    
                    string msg = string.Format("There was an COM Exception when erasing disc\n\n{0}", ex.Message);
                    
                    BeginInvoke(new MethodInvoker(() => MessageBox.Show(this, msg, @"IDiscFormat2.EraseMedia Error", MessageBoxButtons.OK, MessageBoxIcon.Stop)));
                }

                //
                // Remove the Update progress event handler
                //
                discFormatErase.Update -= discFormatErase_Update;

                //
                // Eject the media 
                //
                if (chkEjectFormat.Checked)
                {
                    discRecorder.EjectMedia();
                }

            }
            catch (COMException ex)
            {
                //
                // If anything happens during the format, show the message
                //
                string msg = string.Format("There was an COM Exception when formatting disc\n\n{0}", ex.Message);

                BeginInvoke(new MethodInvoker(() => MessageBox.Show(this, msg, @"COMExeption Error", MessageBoxButtons.OK, MessageBoxIcon.Error)));
            }
            finally
            {
                if (discRecorder != null)
                {
                    Marshal.ReleaseComObject(discRecorder);
                }

                if (discFormatErase != null)
                {
                    Marshal.ReleaseComObject(discFormatErase);
                }
            }
        }

        /// <summary>
        /// Event Handler for the Erase Progress Updates
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="elapsedSeconds"></param>
        /// <param name="estimatedTotalSeconds"></param>
        void discFormatErase_Update([In, MarshalAs(UnmanagedType.IDispatch)] object sender, int elapsedSeconds, int estimatedTotalSeconds)
        {
            var percent = elapsedSeconds * 100 / estimatedTotalSeconds;
            //
            // Report back to the UI
            //

            int[] userState = {elapsedSeconds, estimatedTotalSeconds};

            backgroundFormatWorker.ReportProgress(percent, userState);
        }

        private void backgroundFormatWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
        {
            var timers = (int[]) e.UserState;

            TimeSpan elapsed = TimeSpan.FromSeconds(timers[0]);
            TimeSpan estimated = TimeSpan.FromSeconds(timers[1]);

            lblFormatStatusText.Text = string.Format("Formatting {0:0#}%", e.ProgressPercentage);
            lblElapsedTime.Text = string.Format("Elapsed time: {0:D2}m:{1:D2}s", elapsed.Minutes, elapsed.Seconds);
            lblEstimatedTime.Text = string.Format("Estimated time: {0:D2}m:{1:D2}s", estimated.Minutes, estimated.Seconds);

            prbFormat.Value = e.ProgressPercentage;
        }

        private void backgroundFormatWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            lblFormatStatusText.Text = (int)e.Result == 0 ? "Finished Formatting Disc!" : "Error Formatting Disc!";

            if ((int) e.Result == 0)
            {
                string msg = string.Format("Finished formatting disc, the disc was sucessfully erased.");
                MessageBox.Show(this, msg, @"Formatting Disc", MessageBoxButtons.OK, MessageBoxIcon.Information);
            }

            prbFormat.Value = 0;

            _isFormatting = false;
            EnableFormatUi(true);
        }

        #endregion

        #region Device ComboBox Methods.

        private void devicesComboBox_SelectedIndexChanged(object sender, EventArgs e)
        {
            var discRecorder = (IDiscRecorder2)cboDevices.Items[cboDevices.SelectedIndex];

            //
            // Verify recorder is supported
            //
            IDiscFormat2Data discFormatData = new MsftDiscFormat2Data();

            if (!discFormatData.IsRecorderSupported(discRecorder))
            {
                string msg = string.Format("The recorder is not supported.");

                MessageBox.Show(this, msg, ClientName, MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
        }

        private void devicesComboBox_Format(object sender, ListControlConvertEventArgs e)
        {
            var discRecorder2 = (IDiscRecorder2)e.ListItem;
            string devicePaths = string.Empty;
            var volumePath = (string)discRecorder2.VolumePathNames.GetValue(0);
            
            foreach (string volPath in discRecorder2.VolumePathNames)
            {
                if (!string.IsNullOrEmpty(devicePaths))
                {
                    devicePaths += ",";
                }
                
                devicePaths += volumePath;
            }

            if (!string.IsNullOrEmpty(devicePaths))
            {
                if (devicePaths.Contains("\\"))
                {
                    devicePaths = devicePaths.Replace("\\", "");
                }
            }

            string productId = discRecorder2.ProductId;
            if (!string.IsNullOrEmpty(productId))
            {
                productId = productId.Trim();
            }
            
            string vendorId = discRecorder2.VendorId.Trim();
            if (!string.IsNullOrEmpty(vendorId))
            {
                vendorId = vendorId.Trim();
            }

            e.Value = string.Format("{0}  {1} [{2}]", devicePaths, vendorId, productId);
        }

        #endregion

        #region Add Files.

        private void AddFiles()
        {
            foreach (string filename in _files)
            {
                if (MediaFile.IsWavProperFormat(filename))
                {
                    var mediaFile = new MediaFile(filename);
                    listBoxFiles.Items.Add(mediaFile);
                }

            UpdateSizeOnDisc();
            EnableBurnButton();
        
            }
        }

        #endregion

        #region Get Media Type.

        /// <summary>
        /// converts an IMAPI_MEDIA_PHYSICAL_TYPE to it's string
        /// </summary>
        /// <param name="mediaType"></param>
        /// <returns></returns>
        private static string GetMediaTypeString(IMAPI_MEDIA_PHYSICAL_TYPE mediaType)
        {
            switch (mediaType)
            {
                case IMAPI_MEDIA_PHYSICAL_TYPE.IMAPI_MEDIA_TYPE_UNKNOWN:
                    return "Unknown Media Type";

                case IMAPI_MEDIA_PHYSICAL_TYPE.IMAPI_MEDIA_TYPE_CDROM:
                    return "CD-ROM";

                case IMAPI_MEDIA_PHYSICAL_TYPE.IMAPI_MEDIA_TYPE_CDR:
                    return "CD-R";

                case IMAPI_MEDIA_PHYSICAL_TYPE.IMAPI_MEDIA_TYPE_CDRW:
                    return "CD-RW";

                case IMAPI_MEDIA_PHYSICAL_TYPE.IMAPI_MEDIA_TYPE_DVDROM:
                    return "DVD ROM";

                case IMAPI_MEDIA_PHYSICAL_TYPE.IMAPI_MEDIA_TYPE_DVDRAM:
                    return "DVD-RAM";

                case IMAPI_MEDIA_PHYSICAL_TYPE.IMAPI_MEDIA_TYPE_DVDPLUSR:
                    return "DVD+R";

                case IMAPI_MEDIA_PHYSICAL_TYPE.IMAPI_MEDIA_TYPE_DVDPLUSRW:
                    return "DVD+RW";

                case IMAPI_MEDIA_PHYSICAL_TYPE.IMAPI_MEDIA_TYPE_DVDPLUSR_DUALLAYER:
                    return "DVD+R Dual Layer";

                case IMAPI_MEDIA_PHYSICAL_TYPE.IMAPI_MEDIA_TYPE_DVDDASHR:
                    return "DVD-R";

                case IMAPI_MEDIA_PHYSICAL_TYPE.IMAPI_MEDIA_TYPE_DVDDASHRW:
                    return "DVD-RW";

                case IMAPI_MEDIA_PHYSICAL_TYPE.IMAPI_MEDIA_TYPE_DVDDASHR_DUALLAYER:
                    return "DVD-R Dual Layer";

                case IMAPI_MEDIA_PHYSICAL_TYPE.IMAPI_MEDIA_TYPE_DISK:
                    return "random-access writes";

                case IMAPI_MEDIA_PHYSICAL_TYPE.IMAPI_MEDIA_TYPE_DVDPLUSRW_DUALLAYER:
                    return "DVD+RW DL";

                case IMAPI_MEDIA_PHYSICAL_TYPE.IMAPI_MEDIA_TYPE_HDDVDROM:
                    return "HD DVD-ROM";

                case IMAPI_MEDIA_PHYSICAL_TYPE.IMAPI_MEDIA_TYPE_HDDVDR:
                    return "HD DVD-R";

                case IMAPI_MEDIA_PHYSICAL_TYPE.IMAPI_MEDIA_TYPE_HDDVDRAM:
                    return "HD DVD-RAM";

                case IMAPI_MEDIA_PHYSICAL_TYPE.IMAPI_MEDIA_TYPE_BDROM:
                    return "Blu-ray DVD (BD-ROM)";

                case IMAPI_MEDIA_PHYSICAL_TYPE.IMAPI_MEDIA_TYPE_BDR:
                    return "Blu-ray media";

                case IMAPI_MEDIA_PHYSICAL_TYPE.IMAPI_MEDIA_TYPE_BDRE:
                    return "Blu-ray Rewritable media";
                default:
                    return "Could not get mediatype";
            }
        }

        #endregion

        #region Detect Media.

        private void DetectMedia()
        {
            int index = 0;
            if (cboDevices.InvokeRequired)
            {
                Invoke(new MethodInvoker(delegate() { index = cboDevices.SelectedIndex; }));
            }
            else
            {
                index = cboDevices.SelectedIndex;
            }

            if (index == -1)     //cboDevices.SelectedIndex == -1)
            {
                return;
            }

            object item = null;
            if (cboDevices.InvokeRequired)
            {
                Invoke(new MethodInvoker(delegate() { item = cboDevices.Items[index]; }));
            }
            else
            {
                item = cboDevices.Items[index];
            }

            var discRecorder = (IDiscRecorder2)item;

            //MsftFileSystemImage fileSystemImage = null;
            MsftDiscFormat2Data discFormatData = null;

            try
            {
                //
                // Create and initialize the IDiscFormat2Data
                //
                discFormatData = new MsftDiscFormat2Data();
                if (!discFormatData.IsCurrentMediaSupported(discRecorder))
                {
                    const string text = @"Media not supported or media is not empty";
                    if (txtMediaType.InvokeRequired)
                    {
                        Invoke((MethodInvoker)delegate { txtMediaType.Text = text; });
                    }
                    else
                    {
                        txtMediaType.Text = text;
                    }
                }
                else
                {
                    //
                    // Get the media type in the recorder
                    //
                    discFormatData.Recorder = discRecorder;
                    IMAPI_MEDIA_PHYSICAL_TYPE mediaType = discFormatData.CurrentPhysicalMediaType;

                    string mediatype = GetMediaTypeString(mediaType);
                    if (txtMediaType.InvokeRequired)
                    {
                        Invoke((MethodInvoker)delegate { txtMediaType.Text = mediatype; });
                    }
                    else
                    {
                        txtMediaType.Text = mediatype;
                    }
                }
            }
            catch (COMException ex)
            {
                string msg = string.Format("There was a COMException when detecting media\n\n{0}", ex.Message);

                MessageBox.Show(this, msg, @"COMException", MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
            finally
            {
                if (discFormatData != null)
                {
                    Marshal.ReleaseComObject(discFormatData);
                }
            }
        }

        #endregion

        #region Update Size On Disc.

        /// <summary>
        /// Updates the capacity progressbar
        /// </summary>
        private void UpdateSizeOnDisc()
        {
            //
            // Calculate the size of the files
            //
            Int64 totalMediaSize = listBoxFiles.Items.Cast<MediaFile>().Sum(mediaFile => mediaFile.SizeOnDisc);

            lblSize.Text = string.Format("Size: {0} MB", totalMediaSize / 1024 / 1024);

            if (totalMediaSize == 0)
            {
                prbSizeOnDisc.Value = 0;
                prbSizeOnDisc.ForeColor = SystemColors.Highlight;
            }
            else
            {
                int percent = (int)((totalMediaSize * 100) / 700000000);
                if (percent > 100)
                {
                    prbSizeOnDisc.Value = 100;
                    prbSizeOnDisc.ForeColor = Color.Red;
                }
                else
                {
                    prbSizeOnDisc.Value = percent;
                    prbSizeOnDisc.ForeColor = SystemColors.Highlight;
                }
            }
        }

        #endregion

        #region Burn Audio.

        /// <summary>
        /// User clicked the "Burn" button
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void buttonBurn_Click(object sender, EventArgs e)
        {
            cdNotify1.Stop();

            if (_isBurning)
            {
                //
                // If we're burning, then the user has pressed the cancel button
                //
                buttonBurn.Enabled = false;
                backgroundWorker.CancelAsync();
            }
            else
            {
                //
                // We want to burn, so disable many selection controls
                //
                _isBurning = true;
                EnableBurnUi(false);

                //
                // Get the unique recorder id and send it to the worker thread
                //
                var discRecorder = (IDiscRecorder2)cboDevices.Items[cboDevices.SelectedIndex];
                _burnData.uniqueRecorderId = discRecorder.ActiveDiscRecorder;
                backgroundWorker.RunWorkerAsync(_burnData);
            }
        }

        /// <summary>
        /// The thread that does the burning of the media
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void backgroundWorker_DoWork(object sender, DoWorkEventArgs e)
        {
            e.Result = 0;

            //var discMaster = new MsftDiscMaster2();
            var discRecorder2 = new MsftDiscRecorder2();

            var burnData = (BurnData)e.Argument;
            discRecorder2.InitializeDiscRecorder(burnData.uniqueRecorderId);

            var trackAtOnce = new MsftDiscFormat2TrackAtOnce();
            trackAtOnce.ClientName = ClientName;
            trackAtOnce.Recorder = discRecorder2;
            _burnData.totalTracks = listBoxFiles.Items.Count;
            _burnData.currentTrackNumber = 0;

            //
            // Prepare the wave file streams
            //
            foreach (MediaFile mediaFile in listBoxFiles.Items)
            {
                //
                // Check if we've cancelled
                //
                if (backgroundWorker.CancellationPending)
                {
                    break;
                }

                //
                // Report back to the UI that we're preparing stream
                //
                _burnData.task = BURN_MEDIA_TASK.BURN_MEDIA_TASK_PREPARING;
                _burnData.filename = mediaFile.ToString();
                _burnData.currentTrackNumber++;

                backgroundWorker.ReportProgress(0, _burnData);
                mediaFile.PrepareStream();
            }

            //
            // Add the Update event handler
            //
            trackAtOnce.Update += new DiscFormat2TrackAtOnce_EventHandler(trackAtOnce_Update);

            trackAtOnce.PrepareMedia();

            //
            // Add Files and Directories to File System Image
            //
            foreach (MediaFile mediaFile in listBoxFiles.Items)
            {
                //
                // Check if we've cancelled
                //
                if (backgroundWorker.CancellationPending)
                {
                    e.Result = -1;
                    break;
                }

                //
                // Add audio track
                //
                _burnData.filename = mediaFile.ToString();
                IStream stream = mediaFile.GetTrackIStream();
                trackAtOnce.AddAudioTrack(stream);
            }


            //
            // Remove the Update event handler
            //
            trackAtOnce.Update -= new DiscFormat2TrackAtOnce_EventHandler(trackAtOnce_Update);

            trackAtOnce.ReleaseMedia();

            cdNotify1.Start();

            discRecorder2.EjectMedia();
        }

        /// <summary>
        /// Update notification from IDiscFormat2TrackAtOnce
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="progress"></param>
        void trackAtOnce_Update(object sender, object progress)
        {
            //
            // Check if we've cancelled
            //
            if (backgroundWorker.CancellationPending)
            {
                var trackAtOnce = (IDiscFormat2TrackAtOnce)sender;
                trackAtOnce.CancelAddTrack();
                return;
            }

            var eventArgs = (IDiscFormat2TrackAtOnceEventArgs)progress;

            _burnData.task = BURN_MEDIA_TASK.BURN_MEDIA_TASK_WRITING;

            //
            // IDiscFormat2TrackAtOnceEventArgs Interface
            //
            _burnData.currentTrackNumber = eventArgs.CurrentTrackNumber;
            _burnData.elapsedTime = eventArgs.ElapsedTime;
            _burnData.remainingTime = eventArgs.RemainingTime;

            //
            // IWriteEngine2EventArgs Interface
            //
            _burnData.currentAction = eventArgs.CurrentAction;
            _burnData.startLba = eventArgs.StartLba;
            _burnData.sectorCount = eventArgs.SectorCount;
            _burnData.lastReadLba = eventArgs.LastReadLba;
            _burnData.lastWrittenLba = eventArgs.LastWrittenLba;
            _burnData.totalSystemBuffer = eventArgs.TotalSystemBuffer;
            _burnData.usedSystemBuffer = eventArgs.UsedSystemBuffer;
            _burnData.freeSystemBuffer = eventArgs.FreeSystemBuffer;

            //
            // Report back to the UI
            //
            backgroundWorker.ReportProgress(0, _burnData);
        }

        /// <summary>
        /// Notification that the burn background thread has completed
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void backgroundWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            if ((int)e.Result == 0)
            {
                string msg = string.Format("The audio disc was successfully created.");

                MessageBox.Show(this, msg, ClientName, MessageBoxButtons.OK, MessageBoxIcon.Information);

                //lblCDProgress.Text = "Finished Burning Disc!";
            }
            else
            {
                string msg = string.Format("There was an error creating the audio disc.\n\nResult = {0}", e.Error);

                MessageBox.Show(this, msg, ClientName, MessageBoxButtons.OK, MessageBoxIcon.Error);

                //lblCDProgress.Text = "Error Burning Disc!";
            }


            lblStatusText.Text = string.Empty;
            lblBufferStatus.Text = string.Empty;
            lblCDProgress.Text = string.Empty;
            lblCdBurnElapsedTime.Text = string.Empty;
            lblCdBurnEstimatedTime.Text = string.Empty;
            statusProgressBar.Value = 0;
            progressBarCD.Value = 0;

            _isBurning = false;
            EnableBurnUi(true);
            buttonBurn.Enabled = true;

            
        }

        /// <summary>
        /// Update the user interface with the current progress
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void backgroundWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
        {
            BurnData burnData = (BurnData)e.UserState;

            //TimeSpan elapsed = TimeSpan.FromSeconds(burnData.elapsedTime);
            TimeSpan estimated = TimeSpan.FromSeconds(burnData.remainingTime);

            //lblFormatStatusText.Text = string.Format("Formatting {0:0#}%", e.ProgressPercentage);
            //lblCdBurnElapsedTime.Text = string.Format("Elapsed time: {0:D2}m:{1:D2}s", elapsed.Minutes, elapsed.Seconds);
            lblCdBurnEstimatedTime.Text = string.Format("Remaining time: {0:D2}m:{1:D2}s", estimated.Minutes, estimated.Seconds);

            if (burnData.task == BURN_MEDIA_TASK.BURN_MEDIA_TASK_PREPARING)
            {
                //
                // Notification that we're preparing a stream
                //
                lblCDProgress.Text = string.Format("Preparing stream for {0}", burnData.filename);
                progressBarCD.Value = (int)burnData.currentTrackNumber;
                progressBarCD.Maximum = burnData.totalTracks;
            }
            else if (burnData.task == BURN_MEDIA_TASK.BURN_MEDIA_TASK_WRITING)
            {
                switch (burnData.currentAction)
                {
                    case IMAPI_FORMAT2_TAO_WRITE_ACTION.IMAPI_FORMAT2_TAO_WRITE_ACTION_PREPARING:
                        lblCDProgress.Text = string.Format("Writing Track {0} ({1} of {2})",
                        burnData.filename, burnData.currentTrackNumber, burnData.totalTracks);
                        progressBarCD.Value = (int)burnData.currentTrackNumber;
                        progressBarCD.Maximum = burnData.totalTracks;
                        break;

                    case IMAPI_FORMAT2_TAO_WRITE_ACTION.IMAPI_FORMAT2_TAO_WRITE_ACTION_WRITING:
                        long writtenSectors = burnData.lastWrittenLba - burnData.startLba;

                        if (writtenSectors > 0 && burnData.sectorCount > 0)
                        {
                            int percent = (int)((100 * writtenSectors) / burnData.sectorCount);
                            lblStatusText.Text = string.Format("Progress: {0}%", percent);
                            statusProgressBar.Value = percent;

                            int bufferPercent = (int)((100 * burnData.usedSystemBuffer) / burnData.totalSystemBuffer);
                            lblBufferStatus.Text = string.Format("Buffer: {0}%", bufferPercent);
                            prbBuffer.Value = bufferPercent;
                        }
                        else
                        {
                            lblStatusText.Text = @"Track Progress 0%";
                            statusProgressBar.Value = 0;
                        }
                        break;

                    case IMAPI_FORMAT2_TAO_WRITE_ACTION.IMAPI_FORMAT2_TAO_WRITE_ACTION_FINISHING:
                        lblStatusText.Text = @"Finishing...";
                        break;
                }
            }
        }

        #endregion

        #region Add/Remove Media to ListBox.

        private void buttonAdd_Click(object sender, EventArgs e)
        {
            if (openFileDialog.ShowDialog(this) == DialogResult.OK)
            {
                foreach (string filename in openFileDialog.FileNames)
                {
                    if (MediaFile.IsWavProperFormat(filename))
                    {
                        MediaFile mediaFile = new MediaFile(filename);
                        listBoxFiles.Items.Add(mediaFile);
                    }
                }

                UpdateSizeOnDisc();
                EnableBurnButton();
            }

        }

        private void buttonRemove_Click(object sender, EventArgs e)
        {
            MediaFile mediaFile = (MediaFile)listBoxFiles.SelectedItem;
            if (mediaFile == null)
                return;

            if (MessageBox.Show("Are you sure you want to remove \"" + mediaFile + "\"?",
                "Remove item", MessageBoxButtons.YesNo, MessageBoxIcon.Question) == DialogResult.Yes)
            {
                listBoxFiles.Items.Remove(mediaFile);

                EnableBurnButton();
                UpdateSizeOnDisc();
            }
        }

        /// <summary>
        /// Enable the Burn Button if items in the file listbox
        /// </summary>
        private void EnableBurnButton()
        {
            buttonBurn.Enabled = (listBoxFiles.Items.Count > 0);
        }


        /// <summary>
        /// Enables/Disables the "Burn" User Interface
        /// </summary>
        /// <param name="enable"></param>
        void EnableBurnUi(bool enable)
        {
            buttonBurn.Text = enable ? "&Burn" : "&Cancel";

            cboDevices.Enabled = enable;
            listBoxFiles.Enabled = enable;
            txtMediaType.Enabled = enable;
            buttonAdd.Enabled = enable;
            buttonRemove.Enabled = enable;
        }

        /// <summary>
        /// DrawItem event for listBoxFiles ListBox
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void listBoxFiles_DrawItem(object sender, DrawItemEventArgs e)
        {
            if (e.Index == -1)
            {
                return;
            }

            MediaFile mediaFile = (MediaFile)listBoxFiles.Items[e.Index];
            if (mediaFile == null)
            {
                return;
            }

            e.DrawBackground();

            if ((e.State & DrawItemState.Focus) != 0)
            {
                e.DrawFocusRectangle();
            }

            e.Graphics.DrawIcon(mediaFile.FileIcon, 4, e.Bounds.Y + 4);

            RectangleF rectF = new RectangleF(e.Bounds.X + 24, e.Bounds.Y,
                e.Bounds.Width - 24, e.Bounds.Height);

            Font font = new Font(FontFamily.GenericSansSerif, 10);

            StringFormat stringFormat = new StringFormat();
            stringFormat.LineAlignment = StringAlignment.Center;
            stringFormat.Alignment = StringAlignment.Near;
            stringFormat.Trimming = StringTrimming.EllipsisCharacter;

            e.Graphics.DrawString(mediaFile.ToString(), font, new SolidBrush(e.ForeColor),
                rectF, stringFormat);

        }

        #endregion

        private void tabAudioBurner_Selecting(object sender, TabControlCancelEventArgs e)
        {
            if (_isBurning || _isFormatting)
            {
                e.Cancel = true;
            }
        }

        private void cdNotify1_CdMediaChanged(object sender, GDFlacTools.Controls.CDNotify.CdNotifyEventArgs args)
        {
            DetectMedia();
        }
    }

    public enum BURN_MEDIA_TASK
    {
        BURN_MEDIA_TASK_PREPARING,
        BURN_MEDIA_TASK_WRITING
    }

    public class BurnData
    {
        public string uniqueRecorderId;
        public int totalTracks;
        public string filename;
        public BURN_MEDIA_TASK task;

        // IDiscFormat2DataEventArgs Interface
        public long elapsedTime;		// Elapsed time in seconds
        public long remainingTime;		// Remaining time in seconds
        public long currentTrackNumber;	// current track
        // IWriteEngine2EventArgs Interface
        public IMAPI_FORMAT2_TAO_WRITE_ACTION currentAction;
        public long startLba;			// the starting lba of the current operation
        public long sectorCount;		// the total sectors to write in the current operation
        public long lastReadLba;		// the last read lba address
        public long lastWrittenLba;	// the last written lba address
        public long totalSystemBuffer;	// total size of the system buffer
        public long usedSystemBuffer;	// size of used system buffer
        public long freeSystemBuffer;	// size of the free system buffer
    }

    public class WaveFile
    {
        public riffChunk maindata;
        public fmtChunk format;
        public factChunk fact;
        public dataChunk data;
    }
}
